package uk.ac.manchester.tewpl.iceiface.client.explorer.nodes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.extjs.gxt.ui.client.Registry;
import com.extjs.gxt.ui.client.Style.Scroll;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MessageBoxEvent;
import com.extjs.gxt.ui.client.util.Point;
import com.extjs.gxt.ui.client.widget.Dialog;
import com.extjs.gxt.ui.client.widget.Info;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.Window;
import com.extjs.gxt.ui.client.widget.layout.BoxLayout;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML;

import uk.ac.manchester.tewpl.iceiface.client.Functors.IterativeLoader;
import uk.ac.manchester.tewpl.iceiface.client.Functors.VoidCallback;
import uk.ac.manchester.tewpl.iceiface.client.explorer.Explorer;
import uk.ac.manchester.tewpl.iceiface.client.explorer.FilterResult;
import uk.ac.manchester.tewpl.iceiface.client.explorer.events.UpdatedResultsEvent;
import uk.ac.manchester.tewpl.iceiface.client.explorer.types.PointD;
import uk.ac.manchester.tewpl.iceiface.client.explorer.types.Uri;
import uk.ac.manchester.tewpl.iceiface.client.explorer.ui.AirportPanel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBClassModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBIndividualModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBObjectPropertyModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBWorkspaceModel;
import uk.ac.manchester.tewpl.iceiface.client.service.KBServiceAsync;

public class Airport extends Node {
	
	// KnowledgeBase variables
	private KBServiceAsync kbservice = Registry.get("kbservice");
	private KBWorkspaceModel ws = Registry.get("workspace");
	
	// Explorer (for masking)
	private Explorer explorer = Registry.get("explorer");
	
	// Airport variables
	private Uri uri;
	private String mapLabel;
	private KBClassModel airport;
	private Collection<Terminal> terminals;
	private Collection<Terminal> exceptionalTerminals;
	private Collection<Uri> destinations;
	
	// CurriculumMap position details
	private PointD relativeOffset;
	private Point canvasPosition;
	private Point labelOffset;
	
	// Journey variables
	private Airport nextInJourney;
	private Airport previousInJourney;
	
	// Filter varaibles
	private Collection<FilterResult> filterResults;
	private Collection<String> filterKeywords = new ArrayList<String>();
	private boolean filterIsApplied;
	
	/*
	 * Contructor for Airport object
	 */
	public Airport(final String classUri, String lbl, final AsyncCallback<Airport> callback) {
		// Store the uri, label and callback
		uri = new Uri(classUri);
		mapLabel = lbl;
		
		// Find the KBClassModel for the URI		
		kbservice.findClass(classUri, ws, new AsyncCallback<KBClassModel>() {
			
			// Cannot find class
			@Override
			public void onFailure(Throwable e) {
				MessageBox.alert("Error", "Couldn't find airport class for URI:\n" + classUri + "\n\n" + e.getMessage(), null);
			}
			
			// Found class
			@Override
			public void onSuccess(KBClassModel cls) {
				airport = cls; // Store KBClassModel
				findTerminals(callback); // Find and store terminals
			}
			
		}); // findClass
	} // new Airport
	
	/*
	 * Method used for a-sync calls to throw this Airport instance back
	 */
	private Airport getAirport() {
		return this;
	}
	
	private void findTerminals(final AsyncCallback<Airport> callback) {
		
		// Find subclasses
		kbservice.findSubClasses(airport, ws, new AsyncCallback<Collection<KBClassModel>>() {
				
			// Cannot find subclasses
			@Override
			public void onFailure(Throwable e) {
				MessageBox.alert("Error", "Couldn't find termials for '" + airport.getLabel() + "' class\n\n" + e.getMessage(), null);
			}
			
			// Found subclasses
			@Override
			public void onSuccess(Collection<KBClassModel> subClasses) {
				terminals = new ArrayList<Terminal>(); // Initialise
				for (KBClassModel subClass : subClasses) // For each KBClassModel, create AirportUri
					terminals.add(new Terminal(subClass, getAirport()));
				
				// Callback to be used when any exceptional terminals are loaded
				VoidCallback proceed = new VoidCallback(){
					@Override
					public void work() {
						initDestinations(callback);
					}
				};
				
				/*
				 * Add exceptional terminals
				 */
				if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Index_Clinical_Situation")) {
					loadExceptionalTerminal(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#ICS_Component"), proceed);
				} else if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Learning_Activity")) {
					loadExceptionalTerminal(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#PBL_Case"), proceed);
				} else {
					proceed.work();
				}

			}
			
		}); // findSubClasses
	} // findTerminals
	
	/*
	 * Method to load exceptional terminal for airport, 
	 * used in special cases to warp ontological model
	 */
	private void loadExceptionalTerminal(Uri uri, final VoidCallback cb) {
		// Find class from kbservice
		kbservice.findClass(uri.toString(), ws, new AsyncCallback<KBClassModel>() {

			@Override
			public void onFailure(Throwable arg0) {
				// Couldn't load, continue anyway
				cb.work();
			}

			@Override
			public void onSuccess(KBClassModel cls) {
				// Add terminal to airport
				addExceptionalTerminal(new Terminal(cls, getAirport()));
				
				// Callback
				cb.work();
			}
		});
		
	}
	
	/*
	 * Add an exceptional terminal to the airport
	 */
	private void addExceptionalTerminal(Terminal terminal) {
		// Ensure exceptional terminals collection is initialised
		if (exceptionalTerminals == null)
			exceptionalTerminals = new ArrayList<Terminal>();
		
		// Add terminal to both arrays
		terminals.add(terminal);
		exceptionalTerminals.add(terminal);
	}
	
	/*
	 * Attempt to find the URIs of the destination airports
	 * -- the findObjectPropertiesForClass method returns a collection, but can't read anything from it!
	 */
	private void findDestinations(final AsyncCallback<Airport> callback) {
		
		// Find classes that restrictions are held with
		kbservice.findObjectPropertiesForClass(airport, new AsyncCallback<Collection<KBObjectPropertyModel>>() {

			// Cannot find restrictions
			@Override
			public void onFailure(Throwable e) {
				MessageBox.alert("Error", "Couldn't find destinations for '" + airport.getLabel() + "' class\n\n" + e.getMessage(), null);
			}
			
			// Found restrictions
			@Override
			public void onSuccess(Collection<KBObjectPropertyModel> props) {
				//restrictions = props;
				// Execute callback to Airport creator class
				if (callback != null) {
					callback.onSuccess(getAirport());
				}
			} // onSuccess
		}); // findObjectPropertiesForClass
		
	} // findDestinations
	
	/*
	 * Return the Airport's label
	 */
	public String getLabel() {
		return airport.getLabel();
	}
	
	/*
	 * Return the Airport's map label (for text wrapping)
	 */
	public String getMapLabel() {
		return mapLabel;
	}
	
	/*
	 * Return the Airport's terminals
	 */
	public Collection<Terminal> getTerminals() {
		return terminals;
	}
	
	/*
	 * Return Airport as a string
	 */
	public String toString() {
		return getLabel();
	} // toString
	
	/*
	 * Get the X-Coordinate
	 */
	public int getX() {
		return canvasPosition.x;
	}
	
	/*
	 * Get the Y-Coordinate
	 */
	public int getY() {
		return canvasPosition.y;
	}
	
	/*
	 * Set the X and Y coordinates with Point object
	 */
	public void setXY(Point p) {
		canvasPosition = p;
	}

	/*
	 * Set the X-Coordinate
	 */
	public void setX(int x) {
		canvasPosition.x = x;
	}
	
	/*
	 * Set the Y-Coordinate
	 */
	public void setY(int y) {
		canvasPosition.y = y;
	}
	
	/*
	 * Get the label offset
	 */
	public Point getLabelOffest() {
		return labelOffset;
	}
	
	/*
	 * Return the URI of the airport
	 */
	public Uri getUri() {
		return uri;
	}
	
	/*
	 * Set the label offset
	 */
	public void setLabelOffset(Point p) {
		labelOffset = p;
	}
	
	/*
	 * Get the relative position
	 */
	public PointD getRelativePosition() {
		return relativeOffset;
	}
	
	/*
	 * Set the relative position
	 */
	public void setRelativePosition(PointD pos) {
		relativeOffset = pos;
	}
	
	/*
	 * Check for airport equality
	 */
	@Override
	public boolean equals(Object o) {
		if (o == null) {
			return false;
		} else if (o == airport) {
			return true;
		} else if (o.getClass() == Airport.class) {
			return uri.equals(((Airport) o).getUri());
		} else if (o.getClass() == Uri.class) {
			return uri.equals(((Uri) o).toString());
		} else if (o.getClass() == String.class) {
			return uri.equals(((String) o).equals(uri));
		} else if (o.getClass() == AirportPanel.class) {
			return airport.equals(((AirportPanel)o).getAirport());
		} else {
			return uri.equals(o);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return uri.hashCode();
	}
	
	/*
	 * Get the routes from the airport
	 */
	public Collection<Uri> getDestinations() {
		return destinations;
	}
	
	private void initDestinations(AsyncCallback<Airport> callback) {
		destinations = new ArrayList<Uri>();
		if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Index_Clinical_Situation")) {
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Intended_Learning_Outcome"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#ICS_Component"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#PBL_Case"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Learning_Activity"));
		} else if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Learning_Activity")) {
			//destinations.add(new AirportUri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Learning_Activity"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Programme_Content"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Intended_Learning_Outcome"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Time_Period"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Assessment"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Tutor_Guidance"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Learning_Resource"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Programme_Content"));
		} else if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Intended_Learning_Outcome")) {
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Competency"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Time_Period"));
		} else if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Competency")) {
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Index_Clinical_Situation"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#ICS_Component"));
		} else if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Time_Period")) {
			//destinations.add(new AirportUri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Time_Period"));
		} else if (uri.equals("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Assessment")) {
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Competency"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Group"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#ICS_Component"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Intended_Learning_Outcome"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Student"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Index_Clinical_Situation"));
			destinations.add(new Uri("http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Time_Period"));
		}
		callback.onSuccess(getAirport());		
	}
	
	/**
	 * Sets the Airport that is next in the journey
	 * @param nextInJourney
	 */
	public void setNextInJourney(Airport nextInJourney) {
		this.nextInJourney = nextInJourney;
	}

	/**
	 * Returns the Airport that is next in the journey
	 * @return
	 */
	public Airport getNextInJourney() {
		return nextInJourney;
	}
	
	/**
	 * Sets the Airport that is next in the journey
	 * @param previousInJourney
	 */
	public void setPreviousInJourney(Airport previousInJourney) {
		this.previousInJourney = previousInJourney;
	}

	/**
	 * Returns the Airport that is next in the journey
	 * @return
	 */
	public Airport getPreviousInJourney() {
		return previousInJourney;
	}
	
	/**
	 * Clears the journey details stored by setNextInJourney()
	 * and setPreviousInJourney()
	 */
	public void clearJourneyDetails() {
		nextInJourney = null;
		previousInJourney = null;
	}
	
	/*--------------------------------
	 * Filter Methods
	 * -------------------------------*/
	
	@Override
	public void addKeywordFilter(final String keyword) {
		if (filterIsApplied) {
			explorer.mask("Running search...");
			ArrayList<FilterResult> revisedResults = new ArrayList<FilterResult>();
			for (FilterResult filterResult : filterResults) {
				String label = filterResult.getIndividual().getLabel();
				String desc = filterResult.getIndividual().getDescription();
				if ((desc != null && desc.matches("(?i).*" + keyword + ".*"))
						|| (label != null && label.matches("(?i).*" + keyword + ".*"))) {
					revisedResults.add(filterResult);
				}
			}
			
			if (revisedResults.size() > 0) {
				filterResults = revisedResults;
				explorer.update(new UpdatedResultsEvent());
			} else {
				MessageBox.info("No Results", "The search returned no results", null);
			}
			
			explorer.unmask();
		} else {
			explorer.mask("Running search...");
			kbservice.findIndividualsWithFilter(keyword, airport, ws, new AsyncCallback<Collection<KBIndividualModel>>() {
				@Override
				public void onFailure(Throwable err) {
					MessageBox.alert("Error", "Filtering failed!\n" + err.toString(), null);
					explorer.unmask();
				}

				@Override
				public void onSuccess(Collection<KBIndividualModel> indivs) {
					if (indivs.size() > 0) {
						filterResults = new ArrayList<FilterResult>();
						for (KBIndividualModel indiv : indivs)
							filterResults.add(new FilterResult(indiv));
						
						getAirport().filterIsApplied = true;
						getAirport().filterKeywords.add(keyword);
						
						explorer.unmask();
						explorer.update(new UpdatedResultsEvent());
					} else {
						MessageBox.info("No Results", "The search returned no results", null);
						explorer.unmask();
					}
				}
			});
		}
	}

	@Override
	public void addResult(FilterResult result) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void addResults(Collection<FilterResult> results) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void clearFilters() {
		filterIsApplied = false;
		filterResults = null;
	}

	@Override
	public int getCount() {
		if (filterIsApplied) {
			return filterResults.size();
		} else {
			int count = 0;
			for (Terminal terminal : terminals)
				count += terminal.getCount();
			count += airport.getMemberCount();
			return count;
		}
	}

	@Override
	public Collection<String> getFilterKeywords() {
		return filterKeywords;
	}

	@Override
	public Collection<FilterResult> getResults() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setFilterIsApplied() {
		// TODO Auto-generated method stub
		
	}

}
